/**************************************************************************
 *                                                                         *
 *  W O R D C L O C K   - A clock that tells the time using words.         *
 *                                                                         *
 * Hardware: Arduino Dumelove with a set of individual LEDs under a word   *
 *            stencil.                                                     *
 *                                                                         *
 *   Copyright (C) 2009  Doug Jackson (doug@doughq.com)                    *
 *   Modified by Justin Shaw 2010                                          *
 *                                                                         *
 ***************************************************************************
 *                                                                         * 
 * This program is free software; you can redistribute it and/or modify    *
 * it under the terms of the GNU General Public License as published by    *
 * the Free Software Foundation; either version 2 of the License, or       *
 * (at your option) any later version.                                     *
 *                                                                         *
 * This program is distributed in the hope that it will be useful,         *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of          *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           *
 * GNU General Public License for more details.                            *
 *                                                                         *
 * You should have received a copy of the GNU General Public License       *
 * along with this program; if not, write to the Free Software             *
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,                   *
 * MA  02111-1307  USA                                                     *
 *                                                                         *
 ***************************************************************************
 * 
 * Revision History
 * 
 * Date  By What
 * 20001025 DRJ Initial Creation of Arduino Version 
 *                      - based on Wordclock.c - from PIC version
 * 20100408 TJS Added Real time clock code.  Converted to DateTime libirary.
 * 20100410 TJS Converted to Time libirary.
 * 20100415 TJS Added Modes
 */
#include <Wire.h>
#include <Time.h>
#define TIME_MSG_LEN  10   // time sync to PC is HEADER followed by unix time_t as ten ascii digits
#define TIME_HEADER  '$'   // Header tag for serial time sync message
#define TIME_REQ '%'
#define VERSION_REQ '^'    // Header tag for software version

// debounce mode button threshold
#define DEBOUNCE_THRESH 200
#define N_MODE 3 // Normal, Blink, Test
#define NOMINAL_MODE 0
#define BLINK_MODE 1
#define TEST_MODE 2

// Display output pin assignments
#define MINUTES Display1=Display1 | (1<<0);
#define MTEN Display1=Display1 | (1<<1)  
#define HALF Display1=Display1 | (1<<2)
#define PAST Display1=Display1 | (1<<3)
#define THREE Display1=Display1 | (1<<4)
#define ITIS Display1=Display1 | (1<<5)
#define TWENTY Display1=Display1 | (1<<6)
#define TO Display1=Display1 | (1<<7)

#define TWO Display2=Display2 | (1<<0)
#define SIX Display2=Display2 | (1<<1)
#define TWELVE Display2=Display2 | (1<<2)
#define HFIVE Display2=Display2 | (1<<3)
#define SEVEN Display2=Display2 | (1<<4)
#define OCLOCK Display2=Display2 | (1<<5)
#define ONE Display2=Display2 | (1<<6)
#define QUARTER Display2=Display2 | (1<<7)

#define EIGHT Display3=Display3 | (1<<0)
#define MFIVE Display3=Display3 | (1<<1)
#define ARDUINO Display3=Display3 | (1<<2)
#define ELEVEN Display3=Display3 | (1<<3)
#define HTEN Display3=Display3 | (1<<4)
#define NINE Display3=Display3 | (1<<5)
#define FOUR Display3=Display3 | (1<<6)
#define DOUG Display3=Display3 | (1<<7)
#define DBG_PIN 13
#define MODE_PIN 2

static unsigned long msTick =0;  // the number of Millisecond Ticks since we last 
// incremented the second counter

boolean print_flag; // flag to enable printing every minute (disable in loop())

char Display1=0, Display2=0, Display3=0;
byte ExtRTC; // Use realtime clock?


// hardware constants
const int LEDClockPin=5;
const int LEDDataPin=3;
const int LEDStrobePin=4;
const int FWDButtonPin=6;
const int REVButtonPin=7;

unsigned long last_mode_time = 0;
byte mode = NOMINAL_MODE;
/*
 * Called when mode button is pressed
 * Increment mode after debounce check.
 */
void mode_interrupt(){
  unsigned long now = millis();
  if(now - last_mode_time > DEBOUNCE_THRESH){
    mode++;
    mode %= N_MODE;
    Serial.println(mode, DEC);
  }
  last_mode_time = now;
}

void setup()
{
  // initialise the hardware 
  // initialize the appropriate pins as outputs:
  pinMode(LEDClockPin, OUTPUT); 
  pinMode(LEDDataPin, OUTPUT); 
  pinMode(LEDStrobePin, OUTPUT); 
  pinMode(FWDButtonPin, OUTPUT); 
  pinMode(REVButtonPin, INPUT); 
  pinMode(DBG_PIN, OUTPUT);
  pinMode(MODE_PIN, INPUT);

  attachInterrupt(0, mode_interrupt, FALLING);
  
  Serial.begin(9600);   // setup the serial port to 9600 baud
  delay(100);

  // start Wire proto
  Wire.begin();
  
  setCompileTime();          // default initial time
  setSyncProvider(getTime);  // PC, then RTC
  setSyncInterval(3600);      // update hour (and on boot)
  
  displaytime();             // display the current time
}



void ledsoff(void) {
  Display1=0;
  Display2=0;
  Display3=0;
}

void WriteLEDs(void) {
  // Now we write the actual values to the hardware
  shiftOut(LEDDataPin, LEDClockPin, MSBFIRST, Display3);
  shiftOut(LEDDataPin, LEDClockPin, MSBFIRST, Display2);
  shiftOut(LEDDataPin, LEDClockPin, MSBFIRST, Display1);
  digitalWrite(LEDStrobePin,HIGH);
  delay(2);
  digitalWrite(LEDStrobePin,LOW);    

}

void selftest(void){
  // start by clearing the display to a known state
  ledsoff(); 
  ITIS; 
  WriteLEDs(); 
  delay(500); 
  ledsoff(); 
  MTEN; 
  WriteLEDs(); 
  delay(500); 
  ledsoff(); 
  HALF; 
  WriteLEDs(); 
  delay(500); 
  ledsoff(); 
  TWENTY; 
  WriteLEDs(); 
  delay(500); 
  ledsoff(); 
  QUARTER; 
  WriteLEDs(); 
  delay(500); 
  ledsoff(); 
  MFIVE; 
  WriteLEDs(); 
  delay(500); 
  ledsoff(); 
  MINUTES; 
  WriteLEDs(); 
  delay(500); 
  ledsoff(); 
  PAST; 
  WriteLEDs(); 
  delay(500); 
  ledsoff(); 
  TO; 
  WriteLEDs(); 
  delay(500); 
  ledsoff(); 
  ONE; 
  WriteLEDs(); 
  delay(500); 
  ledsoff(); 
  TWO; 
  WriteLEDs(); 
  delay(500); 
  ledsoff(); 
  THREE; 
  WriteLEDs(); 
  delay(500); 
  ledsoff(); 
  FOUR; 
  WriteLEDs(); 
  delay(500); 
  ledsoff(); 
  HFIVE; 
  WriteLEDs(); 
  delay(500); 
  ledsoff(); 
  SIX; 
  WriteLEDs(); 
  delay(500); 
  ledsoff(); 
  SEVEN; 
  WriteLEDs(); 
  delay(500); 
  ledsoff(); 
  EIGHT; 
  WriteLEDs(); 
  delay(500); 
  ledsoff(); 
  NINE; 
  WriteLEDs(); 
  delay(500); 
  ledsoff(); 
  HTEN; 
  WriteLEDs(); 
  delay(500); 
  ledsoff(); 
  ELEVEN; 
  WriteLEDs(); 
  delay(500); 
  ledsoff(); 
  TWELVE; 
  WriteLEDs(); 
  delay(500); 
  ledsoff(); 
  OCLOCK; 
  WriteLEDs(); 
  delay(500); 
  ledsoff(); 
  DOUG; 
  WriteLEDs(); 
  delay(500); 
  ledsoff(); 
  ARDUINO; 
  WriteLEDs(); 
  delay(500); 
  for(int i=0; i<5; i++)
  {
    Display1=255; 
    Display2=255; 
    Display3=255;
    WriteLEDs(); 
    delay(500);
    ledsoff(); 
    WriteLEDs();
    delay(500);
  }
}
int append_msg(char *msg, char *end, int index){
  byte i;
  for(i = 0; end[i] > 0; i++){
    msg[index++] = end[i];
  }
  return index;
}

char msg[100];
char* displaytime(void){
  byte msg_index = 0;

  // start by clearing the display to a known state
  ledsoff();

  // Now, turn on the "It is" leds
  ITIS;
  //Serial.print("It is ");
  msg_index = append_msg(msg, "It is ", msg_index);  
  // now we display the appropriate minute counter
  if ((minute()>4) && (minute()<10)) { 
    MFIVE; 
    MINUTES; 
    //Serial.print("Five Minutes ");
    msg_index = append_msg(msg, "Five Minutes ", msg_index);
  } 
  if ((minute()>9) &&(minute()<15)) { 
    MTEN; 
    MINUTES; 
    //Serial.print("Ten Minutes ");
    msg_index = append_msg(msg, "Ten Minutes ", msg_index);
  }
  if ((minute()>14) && (minute()<20)) {
    QUARTER; 
    // Serial.print("Quarter ");
    msg_index = append_msg(msg, "Quarter ", msg_index);
  }
  if ((minute()>19) && (minute()<25)) { 
    TWENTY; 
    MINUTES; 
    //Serial.print("Twenty Minutes ");
    msg_index = append_msg(msg, "Twenty Minutes ", msg_index);
  }
  if ((minute()>24) && (minute()<30)) { 
    TWENTY; 
    MFIVE; 
    MINUTES;
    // Serial.print("Twenty Five Minutes ");
    msg_index = append_msg(msg, "Twenty-Five Minutes ", msg_index);
  }  
  if ((minute()>29) && (minute()<35)) {
    HALF;
    // Serial.print("Half ");
    msg_index = append_msg(msg, "Half ", msg_index);
  }
  if ((minute()>34) && (minute()<40)) { 
    TWENTY; 
    MFIVE; 
    MINUTES;
    // Serial.print("Twenty Five Minutes ");
    msg_index = append_msg(msg, "Twenty-Five Minutes ", msg_index);
  }  
  if ((minute()>39) && (minute()<45)) { 
    TWENTY; 
    MINUTES; 
    //Serial.print("Twenty Minutes ");
    msg_index = append_msg(msg, "Twenty Minutes ", msg_index);
  }
  if ((minute()>44) && (minute()<50)) {
    QUARTER; 
    // Serial.print("Quarter ");
    msg_index = append_msg(msg, "Quarter ", msg_index);

  }
  if ((minute()>49) && (minute()<55)) { 
    MTEN; 
    MINUTES; 
    // Serial.print("Ten Minutes ");
    msg_index = append_msg(msg, "Ten Minutes ", msg_index);
  } 
  if (minute()>54) { 
    MFIVE; 
    MINUTES; 
    //Serial.print("Five Minutes ");
    msg_index = append_msg(msg, "Five Minutes ", msg_index);
  }

  if ((minute() <5))
  {
    switch (hour() % 12) {
    case 1: 
      ONE; 
      // Serial.print("One ");
      msg_index = append_msg(msg, "One ", msg_index);
      break;
    case 2: 
      TWO; 
      //Serial.print("Two ");
      msg_index = append_msg(msg, "Two ", msg_index);
      break;
    case 3: 
      THREE; 
      // Serial.print("Three ");
      msg_index = append_msg(msg, "Three ", msg_index);
      break;
    case 4: 
      FOUR; 
      //Serial.print("Four ");
      msg_index = append_msg(msg, "Four ", msg_index);
      break;
    case 5: 
      HFIVE; 
      //Serial.print("Five ");
      msg_index = append_msg(msg, "Five ", msg_index);
      break;
    case 6: 
      SIX; 
      //Serial.print("Six ");
      msg_index = append_msg(msg, "Six ", msg_index);
      break;
    case 7: 
      SEVEN; 
      // Serial.print("Seven ");
      msg_index = append_msg(msg, "Seven ", msg_index);
      break;
    case 8: 
      EIGHT; 
      //Serial.print("Eight ");
      msg_index = append_msg(msg, "Eight ", msg_index);
      break;
    case 9: 
      NINE; 
      //Serial.print("Nine ");
      msg_index = append_msg(msg, "Nine ", msg_index);
      break;
    case 10: 
      HTEN; 
      //Serial.print("Ten ");
      msg_index = append_msg(msg, "Ten ", msg_index);
      break;
    case 11: 
      ELEVEN; 
      //Serial.print("Eleven ");
      msg_index = append_msg(msg, "Eleven ", msg_index);
      break;
    case 0: 
      TWELVE; 
      //Serial.print("Twelve ");
      msg_index = append_msg(msg, "Twelve ", msg_index);
      break;
    }
    OCLOCK;
    //Serial.println("O'Clock");
    msg_index = append_msg(msg, "O'Clock ", msg_index);
  }
  else
    if ((minute() < 35) && (minute() >4))
    {
      PAST;
      // Serial.print("Past ");
      msg_index = append_msg(msg, "Past ", msg_index);
      switch (hour() % 12) {
      case 1: 
        ONE; 
        //Serial.println("One ");
        msg_index = append_msg(msg, "One ", msg_index);
        break;
      case 2: 
        TWO; 
        //Serial.println("Two ");
        msg_index = append_msg(msg, "Two ", msg_index);
        break;
      case 3: 
        THREE; 
        //Serial.println("Three ");
        msg_index = append_msg(msg, "Three ", msg_index);
        break;
      case 4: 
        FOUR; 
        //Serial.println("Four ");
        msg_index = append_msg(msg, "Four ", msg_index);
        break;
      case 5: 
        HFIVE; 
        //Serial.println("Five ");
        msg_index = append_msg(msg, "Five ", msg_index);
        break;
      case 6: 
        SIX; 
        //Serial.println("Six ");
        msg_index = append_msg(msg, "Six ", msg_index);
        break;
      case 7: 
        SEVEN; 
        //Serial.println("Seven ");
        msg_index = append_msg(msg, "Seven ", msg_index);
        break;
      case 8: 
        EIGHT; 
        //Serial.println("Eight ");
        msg_index = append_msg(msg, "Eight ", msg_index);
        break;
      case 9: 
        NINE; 
        //Serial.println("Nine ");
        msg_index = append_msg(msg, "Nine ", msg_index);
        break;
      case 10: 
        HTEN; 
        //Serial.println("Ten ");
        msg_index = append_msg(msg, "Ten ", msg_index);
        break;
      case 11: 
        ELEVEN; 
        //Serial.println("Eleven ");
        msg_index = append_msg(msg, "Eleven ", msg_index);
        break;
      case 0: 
        TWELVE; 
        //Serial.println("Twelve ");
        msg_index = append_msg(msg, "Twelve ", msg_index);
        break;
      }
    }
    else
    {
      // if we are greater than 34 minutes past the hour then display
      // the next hour, as we will be displaying a 'to' sign
      TO;
      //Serial.print("To ");
      msg_index = append_msg(msg, "To ", msg_index);
      switch (hour() % 12) {
      case 1: 
        TWO; 
        //Serial.println("Two ");
        msg_index = append_msg(msg, "Two ", msg_index);
        break;
      case 2: 
        THREE; 
        //Serial.println("Three ");
        msg_index = append_msg(msg, "Three ", msg_index);
        break;
      case 3: 
        FOUR; 
        //Serial.println("Four ");
        msg_index = append_msg(msg, "Four ", msg_index);
        break;
      case 4: 
        HFIVE; 
        //Serial.println("Five ");
        msg_index = append_msg(msg, "Five ", msg_index);
        break;
      case 5: 
        SIX; 
        //Serial.println("Six ");
        msg_index = append_msg(msg, "Six ", msg_index);
        break;
      case 6: 
        SEVEN; 
        //Serial.println("Seven ");
        msg_index = append_msg(msg, "Seven ", msg_index);
        break;
      case 7: 
        EIGHT; 
        //Serial.println("Eight ");
        msg_index = append_msg(msg, "Eight ", msg_index);
        break;
      case 8: 
        NINE; 
        //Serial.println("Nine ");
        msg_index = append_msg(msg, "Nine ", msg_index);
        break;
      case 9: 
        HTEN; 
        //Serial.println("Ten ");
        msg_index = append_msg(msg, "Ten ", msg_index);
        break;
      case 10: 
        ELEVEN; 
        //Serial.println("Eleven ");
        msg_index = append_msg(msg, "Eleven ", msg_index);
        break;
      case 11: 
        TWELVE; 
        //Serial.println("Twelve ");
        msg_index = append_msg(msg, "Twelve ", msg_index);
        break;
      case 0: 
        ONE; 
        //Serial.println("One ");
        msg_index = append_msg(msg, "One ", msg_index);
        break;
      }
    }
  WriteLEDs();
  msg[msg_index++] = '\0';
  return msg;
}


void setCompileTime(){
  char *date = __DATE__;
  char *tm   = __TIME__;
  byte i;
  int yyyy = 0;
  int hh = 0;
  int mm = 0;
  int ss = 0;
  int dd = 0;
  for(i = 0; i<4; i++){
    yyyy = (10 * yyyy) + (date[7 + i] - '0');
  }
  for(i = 0; i < 2; i++){
    hh = (10 * hh) + (tm[0 + i] - '0');
    mm = (10 * mm) + (tm[3 + i] - '0');
    ss = (10 * ss) + (tm[6 + i] - '0');
    dd = (10 * dd) + (date[4 + i] - '0');
  }

  long mmm = (date[0] - 'A') * (26 * 26) + 
    (date[1] - 'a') * 26 + 
    date[2] - 'a';
  switch (mmm) {
  case 6097:
    mmm = 1;
    break;
  case 3485:
    mmm = 2;
    break;
  case 8129:
    mmm = 3;
    break;
  case 407:
    mmm = 4;
    break;
  case 8136:
    mmm = 5;
    break;
  case 6617:
    mmm = 6;
    break;
  case 6615:
    mmm = 7;
    break;
  case 526:
    mmm = 8;
    break;
  case 12287:
    mmm = 9;
    break;
  case 9535:
    mmm = 10;
    break;
  case 9173:
    mmm = 11;
    break;
  case 2134:
    mmm = 12;
    break;
  default:
    mmm = 1;
    break;
  }
  /*
Jan 6097
   Feb 3485
   Mar 8129
   Apr 407
   May 8136
   Jun 6617
   Jul 6615
   Aug 526
   Sep 12287
   Oct 9535
   Nov 9173
   Dec 2134
   */

  setTime(hh, mm, ss, dd, mmm, yyyy);
}
void SWversion(void) {
  //delay(2000);
  Serial.println();
  Serial.println("Wordclock -Arduino v2.0");
  Serial.println("(c)2009 Doug Jackson");
  Serial.println("(c)2010 RealTime clock update by Justin Shaw");
  Serial.print("Compiled on:");
  Serial.print(__DATE__);
  Serial.print(" ");
  Serial.println(__TIME__);
}

void printTime(int year, int month, int day, int hour, int minute, int second){
  Serial.print(year);
  Serial.print("/");
  Serial.print(month);
  Serial.print("/");
  Serial.print(day);
  Serial.print(" ");
  Serial.print(hour);
  Serial.print(":");
  if(minute < 10){
    Serial.print("0");
  }
  Serial.print(minute);
  Serial.print(":");
  if(second < 10){
    Serial.print("0");
  }
  Serial.println(second);
}

boolean PCgetTime(){
  char header;

  boolean status = false;
  while(Serial.available() > 0){
    header = Serial.read();

    // service incoming requests
    if(header == TIME_REQ){
      Serial.println(msg);
      printTime(year(), month(), day(), hour(), minute(), second());
    }
    if(header == VERSION_REQ){
      SWversion();
    }
    // if time sync available from serial port, update time and return true
    // time message consists of a header and ten ascii digits
    else if(header == TIME_HEADER) {  
      time_t pctime = 0;
      status = true; // looks like the start of a time message
      delay(10); // wait for rest of message
      for(int i = 0; i < TIME_MSG_LEN; i++){   
        if(Serial.available()){
          char c= Serial.read();          
          if( c >= '0' && c <= '9'){   
            pctime = (10 * pctime) + (c - '0') ; // convert digits to a number    
          }
          else{
            // nope, did not get valid time message
            status = false;
            break;
          }
        }
        else{
          status = false;
        }
      }
      if(status){
        Serial.println(pctime);
        setTime(pctime);   // Sync Arduino clock to the time received on the serial port
        Serial.print("PC Time:");
        printTime(year(), month(), day(), hour(), minute(), second());
        sync_time();     // update RTC if available
        Serial.print("ARD Time:");
        printTime(year(), month(), day(), hour(), minute(), second());
        status = true;
      }
    }  
  }
  return status;  //if no message return false
}

// get time from RTC
int bcd2dec(byte bcd){
  return (((bcd & 0b11110000)>>4)*10 + 
    (bcd & 0b00001111));
}
boolean RTCgetTime(){
  boolean status;
  // reused from macetech.com sample code
  Wire.beginTransmission(104); // 104 is DS3231 device address
  Wire.send(0); // start at register 0
  Wire.endTransmission();
  Wire.requestFrom(104, 7); // request six bytes (second, minute, hour, day, month, year)
  status = Wire.available();
  if(status){
    int ss = bcd2dec(Wire.receive()); // get seconds
    int mm = bcd2dec(Wire.receive()); // get minutes
    int hh = bcd2dec(Wire.receive());   // get hours
    Wire.receive(); // day of week is discarded
    int DD = bcd2dec(Wire.receive());   // get day
    int MM = bcd2dec(Wire.receive());   // get month
    int YY = bcd2dec(Wire.receive());   // get year
    setTime(hh, mm, ss, DD, MM, YY);
    //printTime(year(), month(), day(), hour(), minute(), second());
  }
  return status;
}

/*
 * single front end interface to both PCgetTime and RTCgetTime()
 * Uses RTC if available or INT if not.
 * Updates time from PC if available
 */
time_t getTime(){
  if(PCgetTime()){
  }
  else{
    RTCgetTime();
  }
  return now();
}

void sync_time(){
  RTCsetTime(hour(), minute(), second(), day(), month(), year());
}

byte dec2bcd(int dec){
  byte t = dec / 10;
  byte o = dec - t * 10;
  return (t << 4) + o;
}
void RTCsetTime(byte hourIn, byte minuteIn, byte secondIn, byte dayIn, byte monthIn, int yearIn){
  Wire.beginTransmission(104); // 104 is DS3231 device address
  Wire.send(0); // start at register 0

  Wire.send(dec2bcd(secondIn)); //Send seconds as BCD
  Wire.send(dec2bcd(minuteIn)); //Send minutes as BCD
  Wire.send(dec2bcd(hourIn)); //Send hours as BCD
  Wire.send(dec2bcd(weekday())); // dow
  Wire.send(dec2bcd(dayIn)); //Send day as BCD
  Wire.send(dec2bcd(monthIn)); //Send month as BCD
  Wire.send(dec2bcd(yearIn % 1000)); //Send year as BCD
  Wire.endTransmission();  

}

void interact(){
  boolean fwd, rev;

  fwd = digitalRead(FWDButtonPin);
  rev = digitalRead(REVButtonPin);
  if(mode == TEST_MODE){
    selftest();
  }
  else if(fwd){
    Serial.println("FWD, +5 Min");
    time_t t = now();
    t += 5 * 60;
    setTime(t);
    sync_time();
    delay(500);
  }
  else if(rev){
    Serial.println("REV, -5 Min");
    time_t t = now();
    t -= 5 * 60;
    setTime(t);
    sync_time();
    delay(500);
  }
}

void loop(void)
{
  PCgetTime();
  // respond to user input
  interact();
  if(mode == TEST_MODE){
    selftest();
  }
  else{
    displaytime();
    if(second() == 0 && print_flag){
      Serial.println(msg);
      print_flag = false; // print only once every minute
    }
    else if(second() == 1){
      print_flag = true; // print next time around
    }
    if(mode == BLINK_MODE && millis() % 1000 < 50){
      digitalWrite(DBG_PIN, HIGH);
    }
    else{
      digitalWrite(DBG_PIN, LOW);
    }
    delay(10);
  }
}






